# asterisk-outbound.pm
# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

# This file is part of the CarrierClass.net project.
# For more information: http://carrierclass.net/
#
package CCNv2::Provision::Asterisk::outbound;
use strict;
use warnings;
use base qw(CCNCore::Provision::Base);
use CCN::Text;
use CCNCore::Scripting;
use CCNv2::Scripting;

sub run()
{
    my $self = shift;
    return unless defined $self->p->{cp_member};

    my $outboundconf = '';
    
    # Step 1: $ENTERPRISE-outbound-$ROUTE are used in CoS ("outbound-$ROUTE").
    #         They collect the route, then use the POOL-level logic (in $POOL-outbound).
    $self->sql_do_for( <<'SQL', $self->p->{cp_member},
        SELECT enterprise, pk, cid FROM outbound 
        WHERE enterprise IN (SELECT enterprise FROM pool WHERE cp_cluster = ?)
SQL
    sub
    {
        my ($ENTERPRISE,$ROUTE,$setcallerid) = @_;
        
        # Caller-ID override for this route.
        if(defined $setcallerid)
        { $setcallerid = "set(CALLERID(number)=$setcallerid)"; }
        else
        { $setcallerid = "Noop()"; }

        $outboundconf .= <<"EOT";
[${ENTERPRISE}-outbound-${ROUTE}]
exten => _X.,1,Set(ROUTE=${ROUTE})
exten => _X.,2,$setcallerid
exten => _X.,3,GotoIf(\${POOL}?4:5)
exten => _X.,4,Goto(\${POOL}-outbound,\${EXTEN},1)
exten => _X.,5,Goto(${ENTERPRISE}-route-${ROUTE},\${EXTEN},1)
; Single Digit
exten => _X,1,Set(ROUTE=${ROUTE})
exten => _X,2,$setcallerid
exten => _X,3,GotoIf(\${POOL}?4:5)
exten => _X,4,Goto(\${POOL}-outbound,\${EXTEN},1)
exten => _X,5,Goto(${ENTERPRISE}-route-${ROUTE},\${EXTEN},1)
EOT
        # YYY must do better than that if no POOL is available!!
    }); # for @routes

    # Step 2: $POOL-outbound filters the destination numbers (based on the country)
    #         and differentiate emergency vs non-emergency calls.
    # These are used to build the COS on the phones ("outbound" CoS route)
    my $pstnconf = '';


    $self->sql_do_for( <<'SQL', $self->p->{cp_member},
        SELECT enterprise.pstn, pool.pk, enterprise.pk, pool.country, pool.pstnlocal, pool.npa
        FROM enterprise, pool
        WHERE pool.enterprise = enterprise.pk
        AND pool.cp_cluster = ?
SQL
    sub
    {
        my ($PSTN,$POOL,$ENTERPRISE,$COUNTRY,$PSTNLOCAL,$NPA) = @_;
        $PSTN = '9',
        $self->warn ("No PSTN access code specified, assuming '9'.")
            unless defined $PSTN;

        my $LEN_PSTN  = length($PSTN);

        # Route PSTN patterns (to E.164 targets)
        
        return $self->error("You must define country, pstnlocal and npa in pool $POOL.")
            unless defined $COUNTRY and defined $PSTNLOCAL and defined $NPA;

        # The first context is used to remove the ${PSTN} prefix.
        my $pstnconf1 = "[${POOL}-outbound]\n";
        # The second context actually implements the routing.
        my $pstnconf2 = "[${POOL}-outbound-route]\n";

        use CCNv2::pstn_recipes;
        my $recipe = $CCNv2::pstn_recipes::pstn_to_e164_recipes{$COUNTRY};
        
        $self->error("Unknown or unhandled country: $COUNTRY."), next
            unless defined $recipe;
        
        for my $do ( keys %{$recipe} )
        {
            my $how = $recipe->{$do}->($PSTNLOCAL,$NPA);
            my ($pattern,$skip,$keep,$prepend) = @{$how};
            # Remove the ${PSTN} prefix
            $pstnconf1 .= "exten => _${PSTN}${pattern},1,Goto(${POOL}-outbound-route,\${EXTEN:${LEN_PSTN}},1)\n";
            # Route
            $pstnconf2 .= "exten => _${pattern},1,Goto(${ENTERPRISE}-route-\${ROUTE},${prepend}\${EXTEN:${skip}:${keep}},1)'\n";
        }
        $pstnconf .= $pstnconf1 . $pstnconf2;
    }); # for @pools

    foreach("/etc/asterisk/config/extensions.pools-outbound.conf")
    {
        CAT $_,  copyright(';').$pstnconf;
        IF_CHANGED $_, CCNv2::Scripting::AST('extensions reload');
    }

    # Step 3 : The E.164 numbers are then sent the list of trunks defined for each type of destination 
    #          (tollfree, etc.).
    #          Calls to invalid destinations are rejected.

    $self->sql_do_for( <<'SQL', $self->p->{cp_member},
        SELECT enterprise, pk FROM outbound 
        WHERE enterprise IN (SELECT enterprise FROM pool WHERE cp_cluster = ?)
SQL
    sub
    {
        my ($ENTERPRISE,$ROUTE) = @_;
        # [_{ENTERPRISE}_-route-_{ROUTE}_] is defined in the parser;
        # it sends the numbers to the trunks defined in the route.

        our %entries = ();
        our %ranks = ();

        our %aclentries = ();

        # Always send to the Erouter by default.
        $entries{'emergency'} .=
            "exten => emergency,1,Goto(send-to-erouter,s,1)\n".
            "exten => emergency,2,Noop(Emergency: trying trunks)\n";            
        $ranks{'emergency'} = 2;

        $self->sql_do_for( <<'SQL', $ROUTE,
            SELECT destination, trunk, rank FROM outbound_trunk
            WHERE outbound = ? ORDER BY rank ASC
SQL
        sub
        {
            my ($pattern,$trunk) = @_;

            $pattern = "~$pattern~";
            # This is used as a last resort, to route calls from the ERouter (see below).
            $pattern = 'emergency' if $pattern eq '~emergency~';
            $pattern = '_X.'       if $pattern eq '~default~';
            
            my $rank = $ranks{$pattern} || 1;

            my $indexp = $rank*2-1;
            my $index  = $rank*2;
            my $indexn = $rank*2+1;

            $entries{$pattern} .= 
            "exten => ${pattern},$indexp,Gosub(dispatch,TTr${trunk}m\${EXTEN},1)\n".
            # "999" is Return(), see below
            "exten => ${pattern},$index,GotoIf(\${TO_PROXY_OK}?999:$indexn)\n";

            # Only the first trunk is returned (it's an ACL after all).
            $aclentries{$pattern} =
            "exten => ${pattern},1,Set(ACL_OUTPUT=$trunk)\n".
            "exten => ${pattern},2,Return\n"
                if not exists $aclentries{$pattern};

            $ranks{$pattern} = $rank + 1;
        });

        $outboundconf .= "\n[${ENTERPRISE}-route-${ROUTE}]\n";
        for my $pattern (keys %entries)
        {
            my $rank = $ranks{$pattern};
            my $indexp = $rank*2-1;
            $outboundconf .= $entries{$pattern};
            # we finish by adding congestion indication
            $outboundconf .=
            "exten => ${pattern},$indexp,Goto(error-outbound-congestion,s,1)\n".
            "exten => ${pattern},999,Return\n";
        }

        $outboundconf .= "\n[${ENTERPRISE}-acl-route-${ROUTE}]\n";
        $outboundconf .= join('',values %aclentries);
    }); # for @routes

    foreach("/etc/asterisk/config/extensions.outbound.template")
    {
        CAT $_,  copyright(';').$outboundconf;

        CCNv2::Scripting::E164_CHANGED $_, "/etc/asterisk/config/extensions.outbound.conf"
        => CCNv2::Scripting::AST('extensions reload');
    };

    # Step 4 : Emergency calls are handled at the POOL-level.
    # Note: they are always re-written in terms of other routes.
    my $emerconf = '';

    $self->sql_do_for( <<'SQL', $self->p->{cp_member},
        SELECT pk, enterprise FROM pool WHERE cp_cluster = ?
SQL
    sub
    {
        my $POOL = shift;
        my $ENTERPRISE_id = shift;

        $emerconf .= <<"EOT";

[${POOL}-emergency-outbound]
exten => s,1,Noop(${POOL}-emergency-outbound)
EOT

        # If the EResolution fails / is not accessible, fallback to the trunk(s) specified 
        # for the Pool.
        $self->sql_do_for( <<'SQL', $POOL,
            SELECT trunk, rank FROM pool_ertrunk WHERE pool = ? ORDER BY rank ASC
SQL
        sub
        {
            my $trunk = shift;
            $emerconf .= <<"EOT";
exten => s,n,Gosub(trunk-to-proxy,TTr${trunk}memergency,1)
EOT
        });

        # And as a last resort, try to route using the original $ROUTE information,
        # skipping the first instance.
        $emerconf .= <<"EOT";
exten => s,n,Goto(${ENTERPRISE_id}-route-\${ROUTE},emergency,2)
EOT

    }); # for @pools

    foreach("/etc/asterisk/config/extensions.emergency.conf")
    {
        CAT $_, $emerconf;
        IF_CHANGED $_, => CCNv2::Scripting::AST('extensions reload');
    }
}
1;
